﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using A2DFramework.CacheService.Configuration;
using A2DFramework.CacheService.CacheProvider.LocalCache;

namespace A2DFramework.CacheService
{
    public class CacheCore
    {
        private ICacheCoordinator cacheProvider = null;
        private Storage storageSetting;
        public CacheCore(Storage storageSetting, ICacheCoordinator cacheProvider)
        {
            this.cacheProvider = cacheProvider;
            this.storageSetting = storageSetting;
        }

        public void Set(string key, object value, CacheExpireType expireType, double increment)
        {
            if (value == null)
            {
                Remove(key);
                return;
            }
            AssureSerializable(value);
            string xml = Serializer2XMLConvert(value);
            CacheParsedObject parsedObj = new CacheParsedObject();

            string classType = string.Format("{0}", value.GetType().FullName);
            if (xml.Length > storageSetting.DistributedCacheSetting_MaxCacheEntitySize)
            {
                /*
                    key:1,3,ConcreteType
                    key_1:subvalue1
                    key_2:subvalue2
                    key_3:subvalue3
                */
                //拆分成更小的单元
                int splitCount = xml.Length / storageSetting.DistributedCacheSetting_MaxCacheEntitySize;
                if (storageSetting.DistributedCacheSetting_MaxCacheEntitySize * splitCount < xml.Length)
                    splitCount++;
                parsedObj.MainObject = new KeyValuePair<string, string>(key, string.Format("1@{0}@{1}", splitCount, classType));
                for (int i = 0; i < splitCount; i++)
                {
                    if (i == splitCount - 1)  //最后一段，直接截取到最后，不用给出长度
                        parsedObj.SplittedElements.Add(xml.Substring(i * storageSetting.DistributedCacheSetting_MaxCacheEntitySize));
                    else                      //其他，要给出长度
                        parsedObj.SplittedElements.Add(xml.Substring(i * storageSetting.DistributedCacheSetting_MaxCacheEntitySize, storageSetting.DistributedCacheSetting_MaxCacheEntitySize));
                }
            }
            else
            {
                /*
                    key:1,1,ConcreteType
                    key_1:value
                */
                parsedObj.MainObject = new KeyValuePair<string, string>(key, string.Format("1@1@{0}", classType));
                parsedObj.SplittedElements.Add(xml);
            }

            //针对CacheParsedObject进行逐项保存
            this.cacheProvider.Put(parsedObj.MainObject.Key, parsedObj.MainObject.Value, expireType, increment);
            int curIndex = 0;
            foreach (string xmlValue in parsedObj.SplittedElements)
            {
                curIndex++;
                string tkey = string.Format("{0}_{1}", parsedObj.MainObject.Key, curIndex);
                this.cacheProvider.Put(tkey, xmlValue, expireType, increment);
            }
        }

        public void Set(string key, object value)
        {
            Set(key, value, CacheExpireType.Forever, 0);
        }

        public object Get(string key)
        {
            string mainObjKeySetting = (string)cacheProvider.Get(key);
            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)
                return null;

            return _Get(key, mainObjKeySetting);
        }
        public object GetRemote(string key)
        {
            string mainObjKeySetting = (string)cacheProvider.GetRemote(key);
            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)
                return null;

            return _Get(key, mainObjKeySetting);
        }

        private object _Get(string key, string mainObjKeySetting)
        {
            string classType;
            CacheParsedObject parsedObj;
            GetParsedObject(key, mainObjKeySetting, out classType, out parsedObj);

            string xmlValue = string.Empty;
            parsedObj.SplittedElements.ForEach(t => xmlValue += t);

            using (StringReader rdr = new StringReader(xmlValue))
            {
                //Assembly.Load("Core");
                Type t = Type.GetType(classType);
                XmlSerializer serializer = new XmlSerializer(t);
                return serializer.Deserialize(rdr);
            }
        }

        public void Remove(string key)
        {
            string mainObjKeySetting = (string)cacheProvider.Get(key);
            if (mainObjKeySetting == null || mainObjKeySetting.Length == 0)
                return;

            string classType;
            CacheParsedObject parsedObj;
            GetParsedObject(key, mainObjKeySetting, out classType, out parsedObj);

            int i = 1;
            parsedObj.SplittedElements.ForEach(t => this.cacheProvider.Remove(string.Format("{0}_{1}", parsedObj.MainObject.Key, i++)));
            this.cacheProvider.Remove(parsedObj.MainObject.Key);
        }
        private void GetParsedObject(string key, string mainObjKeySetting, out string classType, out CacheParsedObject parsedObj)
        {
            int from = 1, end = 1;
            classType = string.Empty;
            if (mainObjKeySetting.IndexOf('@') > 0)
            {
                end = int.Parse(mainObjKeySetting.Split('@')[1]);
                classType = mainObjKeySetting.Split('@')[2];
            }

            parsedObj = new CacheParsedObject();
            parsedObj.MainObject = new KeyValuePair<string, string>(key, string.Format("1@{0}@{1}", end, classType));
            for (int i = from; i <= end; i++)
                parsedObj.SplittedElements.Add((string)this.cacheProvider.Get(string.Format("{0}_{1}", parsedObj.MainObject.Key, i)));
        }
        private string Serializer2XMLConvert(object value)
        {
            using (StringWriter sw = new StringWriter())
            {
                XmlSerializer xz = new XmlSerializer(value.GetType());
                xz.Serialize(sw, value);
                return sw.ToString();
            } 
        }
        private void AssureSerializable(object value)
        {
            if (value == null)
                throw new Exception("cache object cannot be null");
            if (value.GetType().GetCustomAttributes(typeof(SerializableAttribute), true).Count()<=0
                &&
                !value.GetType().IsValueType)
                throw new Exception("cache object must be Serializable");
        }
    }
}
